#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAX = 102;
bool errFlag = false;

bool blockCheck = true; //块注释检查

string keywords[16] = { "if", "else", "void", "return", "while", "for", "do", "break", "continue", "int", "char", "double", "float", "case", "const" }; //NOLINT
string operators[15] = { "==", "!=", "<=", ">=", "<", ">", "!", "&&", "||", "=", "+", "-", "*", "/", "%" }; //NOLINT
string separaters[8] = { ";", ",", "[", "]", "{", "}", "(", ")" }; //NOLINT

void analyse(string& path);

void illegalWordError(const string& word, int lineNo);

void notMatchBlockCommentError(int lineNo);

bool isLetter(char n);

bool isDigit(char n);

bool isIdentity(string word);

bool isConst(const string& word);

bool isKeyword(const string& word);

bool isOperator(const string& word);

bool isSeparater(const string& word);

void judgeLegality(string& word, int lineNo);

string& toUpperCase(string& s);

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        cout << "Too few arguments." << endl;
        return 1;
    }
    string path{ argv[1] };
    string name = path.substr(0, path.length() - 3); //去掉后缀名
    freopen((name + "_la.txt").c_str(), "w", stdout); //重定向输出
    analyse(path);
    if (errFlag)
        return 1;
    else
        return 0;
}

void analyse(string& path)
{
    int lineNo = 0;
    char str[100] = { 0 };

    FILE* fp = fopen(path.c_str(), "r");
    while (fgets(str, sizeof(str), fp) != nullptr)
    {
        lineNo++;
        string line{ str };
        string word;
        int i, j = 0;
        char wordCandidate[MAX] = { 0 };

        for (i = 0; i <= line.length(); i++)
        {
            char current = line[i];
            char next = line[i + 1];
            string s{ current };
            if (current == '/' && next == '/')
                if (blockCheck) //如果不在块注释中，直接忽略本行后面的内容
                    break;

            if (current == '/' && next == '*')
            {
                blockCheck = false;
                ++i; // 为了防止/*/被认为是块注释而如此设置
                continue;
            }

            if (current == '*' && next == '/')
            {
                if (blockCheck)
                    illegalWordError("*/", lineNo);
                blockCheck = true;
                ++i;
                continue;
            }

            if (blockCheck) // 不在块注释中
            {
                if (current == ' ' || current == '\n' || current == '\t' || current == '\0') // 遇到换行、空格、Tab时处理前一符号串
                {
                    word = wordCandidate;
                    if (!word.empty())
                        judgeLegality(word, lineNo);

                    word.clear();
                    memset(wordCandidate, 0, sizeof(wordCandidate));
                    j = 0;
                }
                else if (isSeparater(s) || isOperator(s))
                {
                    // 处理前一符号串
                    word = wordCandidate;
                    if (!word.empty())
                        judgeLegality(word, lineNo);

                    // 贪心处理双符号运算符
                    string temp = s + next;
                    if (isOperator(temp))
                    {
                        judgeLegality(temp, lineNo);
                        ++i;
                    }
                    else
                        judgeLegality(s, lineNo);

                    word.clear();
                    memset(wordCandidate, 0, sizeof(wordCandidate));
                    j = 0;
                }
                else
                {
                    wordCandidate[j] = current;
                    j++;
                }
            }
        }
    }
    if (!blockCheck) // 在程序读完了再检查块注释有没有闭合
        notMatchBlockCommentError(lineNo);
}

/**
 * @param s 要转换的字符串
 * @return 转换完成的字符串
 * @note 把字符串中所有的小写字母转为大写字母
 */
string& toUpperCase(string& s)
{
    for (char& i : s)
        if (i >= 'a' && i <= 'z')
            i -= 32;
    return s;
}

/**
 * @param word 非法字符序列
 * @param lineNo 出现错误的行号
 * @note 输出出现非法字符时的错误提示
 */
void illegalWordError(const string& word, int lineNo)
{
    errFlag = true;
    cout << "Error: Illegal word error '" << word << "' at line " << lineNo << endl;
}

/**
 * @param lineNo 出现错误的行号
 * @note 输出出现块注释不匹配时的错误提示
 */
void notMatchBlockCommentError(int lineNo)
{
    errFlag = true;
    cout << "Error: Block comment lack of '*/' error at line " << lineNo << endl;
}

/**
 * @param n 要判断的单字符
 * @return 若是字母或者下划线则返回true
 * @note 判断是否为字母或下划线
 */
bool isLetter(char n)
{
    if ((n >= 'a' && n <= 'z') || (n >= 'A' && n <= 'Z') || n == '_')
        return true;
    return false;
}

/**
 * @param n 要判断的单字符
 * @return 若是数字则返回true
 * @note 判断是否为数字
 */
bool isDigit(char n)
{
    if (n >= '0' && n <= '9')
        return true;
    return false;
}

/**
 * @param word 要判断的字符序列
 * @return 若是标识符则返回true
 * @note 判断是否为标识符
 */
bool isIdentity(string word)
{
    if (isDigit(word[0]))
        return false;
    for (int i = 1; word[i]; i++)
        if (!isLetter(word[i]) && !isDigit(word[i])) // 非法字符
            return false;
    return true;
}

/**
 * @param word 要判断的字符序列
 * @return 若是整数常量则返回true
 * @note 判断是否为常量整数
 */
bool isConst(const string& word)
{
    return all_of(word.begin(), word.end(),
            isDigit);
}

/**
 * @param word 要判断的字符序列
 * @return 若是关键词则返回true
 * @note 判断是否是关键词
 */
bool isKeyword(const string& word)
{
    return any_of(keywords, keywords + 16,
            [=](const string& x) -> bool
            { return x == word; });
}

/**
 * @param word 要判断的字符序列
 * @return 若是运算符则返回true 
 * @note 判断是否是运算符
 */
bool isOperator(const string& word)
{
    return any_of(operators, operators + 15,
            [=](const string& x) -> bool
            { return x == word; });
}

/**
 * @param word 要判断的字符序列
 * @return 若是分隔符则返回true
 * @note 判断是否是分隔符
 */
bool isSeparater(const string& word)
{
    return any_of(separaters, separaters + 8,
            [=](const string& x) -> bool
            { return x == word; });
}

/**
 * @param word 要判断的字符序列
 * @param lineNo 字符序列出现的行号
 * @note 检查是否合法，然后选择输出与报错
 */
void judgeLegality(string& word, int lineNo)
{
    if (isKeyword(word))
        cout << "<" << toUpperCase(word) << ", @>" << endl; // 统一形式
    else if (isOperator(word))
        cout << "<OP, " << word << ">" << endl;
    else if (isSeparater(word))
        cout << "<SEP, " << word << ">" << endl;
    else if (isConst(word))
        cout << "<NUM, " << word << ">" << endl;
    else if (isIdentity(word))
        cout << "<ID, " << word << ">" << endl;
    else
        illegalWordError(word, lineNo);
}
